#include "qoscar.h"

//#define OSCARDEBUG

/* Constructor */
QOscar::QOscar(const QString &sn, const QString &password, const QString &server, quint16 port, quint16 tag)
{
    oscarSN = sn;
    oscarPassword = password;
    oscarServer = server;
    oscarPort = port;
    oscarCodec = "Windows-1251";

    oscarState = osOffline;
    oscarStatus = STATUS_ONLINE;
    oscarStatusFlags = STATUS_WEBAWARE | STATUS_BIRTHDAY;
    oscarSequence = 0;
    oscarSent = oscarReceived = 0;
    oscarTag = tag;

    oscarIdent.clientName = "SaMP";
    oscarIdent.majorVersion =1; // 6;   //  1;
    oscarIdent.minorVersion = 0; //5;   //;  0;
    oscarIdent.lesserVersion = 0;// 3;  //  0;
    oscarIdent.build = 104;          // 1;
    oscarIdent.unknown = 0x010A;
    oscarIdent.subBuild = 30007;
    oscarIdent.language ="ru";
    oscarIdent.country = "ru";

//    oscarIdent.clientName = "ICQ Client";
//    oscarIdent.majorVersion =  6;   //  1;
//    oscarIdent.minorVersion =  5;   //;  0;
//    oscarIdent.lesserVersion =  3;  //  0;
//    oscarIdent.build = 104;          // 1;
//    oscarIdent.unknown = 0x010A;
//    oscarIdent.subBuild = 30007;
//    oscarIdent.language ="ru";
//    oscarIdent.country = "ru";

    icbm.setEncoding(oscarCodec);

    connect(&socket, SIGNAL(onConnected()), this, SLOT(onSocketConnected()));
    connect(&socket, SIGNAL(onDisconnected()), this, SLOT(onSocketDisconnected()));
    connect(&socket, SIGNAL(onError(QAbstractSocket::SocketError)),
	    this, SLOT(onSocketError(QAbstractSocket::SocketError)));
    connect(&socket, SIGNAL(onDataRead(QOscarBA)), this, SLOT(onSocketDataRead(QOscarBA)));

    connect(&oservice, SIGNAL(onLoggedIn()), this, SLOT(onOserviceLoggedIn()));

    connect(&icbm, SIGNAL(onMessage(QMessage)), this, SLOT(onIcbmMessage(QMessage)));
    connect(&feedbag, SIGNAL(onRosterReceived()), this, SLOT(onRosterReceived()));
    connect(&feedbag, SIGNAL(onBuddyArrived(QRosterEntry)), this, SLOT(onBuddyArrived(QRosterEntry)));
    connect(&feedbag, SIGNAL(onBuddyDeparted(QRosterEntry)), this, SLOT(onBuddyDeparted(QRosterEntry)));
    connect(&imd, SIGNAL(onOfflineMessage(QOfflineMessage)), this, SLOT(onOfflineMessage(QOfflineMessage)));
    connect(&imd, SIGNAL(onOfflineMessagesDone()), this, SLOT(onOfflineMessagesDone()));

    connect(&timer, SIGNAL(timeout()), this, SLOT(onPingTimer()));
}

/* Start login sequence */
void QOscar::login()
{
    if ( (oscarState != osOffline) && (oscarState != osError) ) {
#ifdef OSCARDEBUG
	qDebug() << "[Main] {Error} You are already online or connecting!";
#endif
	return;
    }

    oscarState = osConnecting;
    socket.connectToServer(oscarServer, oscarPort);
}

/* Logoff */
void QOscar::logoff()
{
    sendFlap(CLOSE_CHANNEL, createCLI__GOODBYE());
    oscarState = osOffline;
    emit onLoggedOff(this);
    socket.disconnectFromServer();
}

/* Roast password */
QByteArray QOscar::roastPassword()
{
    QByteArray result;
    for ( qint32 i = 0; i < oscarPassword.length(); i++ )
    {
        QChar c = oscarPassword.at(i);
        quint8 code = c.toAscii();
        result.append(code ^ QRoastArray[i]);
    }
    return result;
}

/* Increase Sequence */
void QOscar::incSequence()
{
    if ( oscarSequence < 0x8000 )
	oscarSequence++;
    else
	oscarSequence = 0;
}

/* Handle received packet */
void QOscar::handlePacket(const QOscarBA &data)
{
    QOscarBA ba(data);

#ifdef OSCARDEBUG
    qDebug() << "[Main] {Message} Handling FLAPs...";
#endif

    while ( true ) {

	QFlap flap(ba);
	if ( ! flap.isValid() ) {
#ifdef OSCARDEBUG
	    qDebug() << "[Main] {Error} Flap is not valid!" << flap.channel() << ":" << flap.sequence() << ":" << flap.data();
#endif
        ba.clear();
	    break;
	}

	switch ( flap.channel() ) {

	    case NEW_CHANNEL:					    // New connection negonation
#ifdef OSCARDEBUG
		qDebug() << "[Main] {Message} NEW_CHANNEL FLAP";
#endif
		handleNewPacket();
		break;

	    case CLOSE_CHANNEL:					    // Close connection
#ifdef OSCARDEBUG
		qDebug() << "[Main] {Message} CLOSE_CHANNEL FLAP";
#endif
		handleClosePacket(flap.data());
		break;

	    case SNAC_CHANNEL:					    // SNAC channel
		handleSnac(flap.data());
		break;

	    default:
		break;

	}

	if ( flap.isLast() ) {
        ba.remove(0, flap.length() + 6);
#ifdef OSCARDEBUG
	    qDebug() << "[Main] {Message} All FLAPs are handled!";
#endif
	    break;
	}

    if ( (quint32) ba.length() < flap.length() + 6 )
	    break;
    ba.remove(0, flap.length() + 6);
    }
}

/* Handle NEW_CHANNEL Flap */
void QOscar::handleNewPacket()
{
    if ( oscarState == osConnected )
	sendFlap(NEW_CHANNEL, createCLI__IDENT(oscarIdent, oscarSN, roastPassword()), true);
    else
	sendFlap(NEW_CHANNEL, createCLI__COOKIE(oscarBOSCookie), true);
}

/* Handle CLOSE_CHANNEL Flap */
void QOscar::handleClosePacket(const QOscarBA &data)
{
    QOscarBA ba(data);

    while ( true ) {

	QTlv tlv(ba);

	if ( ! tlv.isValid() ) {
#ifdef OSCARDEBUG
	    qDebug() << "[Main] {Error} TLV is not valid!" << tlv.type();
#endif
	    break;
	}

	switch ( tlv.type() ) {

	    case TLV_BOS_IDENT:
		oscarBOSServer = tlv.data();
		oscarBOSServer.chop(5);			    // Dirty hack!!! (deleting port)
		break;

	    case TLV_COOKIE_IDENT:
		oscarBOSCookie = tlv.data();
		oscarState = osConnectingToBOS;
		socket.disconnectFromServer();
		socket.connectToServer(oscarBOSServer, oscarPort);

		break;

	    case RATE_LIMIT_EXCEEDED:			    // Rate limit!
		emit onError(eRateLimit, this);
		oscarState = osError;
		break;

            case 8:
                if ( tlv.data().readU16() == 0x0005 )
                    emit onError(eLogonFailed, this);
                else
                    emit onError(eRateLimit, this);		// HACK!!!
		oscarState = osError;
		break;

	    default:
		break;
	}

	if ( tlv.isLast() ) {
#ifdef OSCARDEBUG
	    qDebug() << "[Main] {Message} All TLVs are parsed!";
#endif
	    break;
	}

	if ( ba.length() < tlv.length() + 2 + 2)
	    break;

	ba.remove(0, tlv.length() + 2 + 2);
    }
}

/* Handling SNAC packet */
void QOscar::handleSnac(const QOscarBA &data)
{
    QSnac snac(data);

    if ( ! snac.isValid() ) {
#ifdef OSCARDEBUG
	qDebug() << "[Main] {Error} SNAC is not valid!";
#endif
	return;
    }

    switch ( snac.group() ) {

	case FOODGROUP_OSERVICE:		// OSERVICE PACKET
	    oservice.handlePacket(snac.data(), snac.type());
	    break;

	case FOODGROUP_ICBM:			// ICBM PACKET
	    icbm.handlePacket(snac);
	    break;

	case FOODGROUP_FEEDBAG:
	    feedbag.handlePacket(snac);
	    break;
    case 3:
            feedbag.handlePacket(snac);
            break;

	default:
	    break;
    }
#ifdef OSCARDEBUG
    qDebug() << "[Main] {Message} SNAC: " << snac.group() << snac.type();
#endif
}

/* Sending FLAP to socket */
void QOscar::sendFlap(quint8 channel, const QOscarBA &data, bool isFirst)
{
    if ( ! socket.isWritable() ) {
#ifdef OSCARDEBUG
	qDebug() << "[Main] {Error} Socket is not writable!";
#endif
	return;
    }

    QFlap flap(channel, oscarSequence, data);
    oscarSent += socket.write(flap.toByteArray(isFirst));
    incSequence();
}

/* Sending SNAC to socket */
void QOscar::sendSnac(quint16 group, quint16 type, quint16 flags, quint32 sequence, const QOscarBA &data)
{
    QSnac snac(group, type, flags, sequence, data);
    sendFlap(SNAC_CHANNEL, snac.toByteArray());
}

/* Sending message */
void QOscar::sendMessage(const QString &sn, const QString &message)
{
    sendFlap(SNAC_CHANNEL, icbm.createICBM__MESSAGE_TO_HOST(sn, message));
}

/* Set web aware and authorization */
void QOscar::setWebAware(bool auth, bool webAware)
{
    sendFlap(SNAC_CHANNEL, imd.createIMD__REQUEST_SET_WEBSTATUS(oscarSN, auth, webAware));
}

/* Set FULL user info */
void QOscar::setInfo(const QOscarUserInfo &info)
{
    sendFlap(SNAC_CHANNEL, imd.createIMD__REQUEST_SET_FULL_INFO(oscarSN, info));
}

/* Send typing notification */
void QOscar::sendTypingNotification(const QString &sn, bool start)
{
    sendFlap(SNAC_CHANNEL, icbm.createICBM__CLIENT_EVENT(sn, (start) ? TYPING_BEGIN : TYPING_END));
}

/* Request roster */
void QOscar::requestRoster()
{
    sendFlap(SNAC_CHANNEL, feedbag.createFEEDBAG__QUERY());
 //   sendFlap(SNAC_CHANNEL, feedbag.createFEEDBAG__USE());
}

/* Request offline messages */
void QOscar::requestOfflineMessages()
{
    sendFlap(SNAC_CHANNEL, imd.createIMD__REQUEST_OFFLINE_MESSAGES(oscarSN));
}

/** ************************************************************ **/
/**		    SLOTS					 **/
/** ************************************************************ **/

/* Socket is connected to host */
void QOscar::onSocketConnected()
{
    if ( oscarState == osConnecting )
	oscarState = osConnected;
    else
	oscarState = osConnectedToBOS;
}

/* Socket is disconnected from host */
void QOscar::onSocketDisconnected()
{
    if ( oscarState != osError )
	oscarState = osOffline;

    if ( timer.isActive() )
	timer.stop();
}

/* Socket Error */
void QOscar::onSocketError(QAbstractSocket::SocketError socketError)
{
    oscarState = osError;
    emit onError(eNetwork, this);
}

/* Socket read some data */
void QOscar::onSocketDataRead(const QOscarBA &data)
{
    oscarReceived += data.length();
    handlePacket(data);
}

/* OSERVICE - Logged in */
void QOscar::onOserviceLoggedIn()
{
    sendFlap(SNAC_CHANNEL, oservice.createOSERVICE__SET_NICKINFO_FIELDS(oscarStatus, oscarStatusFlags)); // Send OSERVICE__SET_NICKINFO_FIEDS
    sendFlap(SNAC_CHANNEL, oservice.createOSERVICE__CLIENT_ONLINE());					 // Send OSERVICE__CLIENT_ONLINE
    //sendFlap(SNAC_CHANNEL, feedbag.createFEEDBAG__USE());						 // For arrived/departed
    oscarState = osOnline;
    emit onLoggedIn(this);
    timer.start(1000 * 60);
}

/* On Incoming message */
void QOscar::onIcbmMessage(const QMessage &message)
{
//    qDebug() << "ICBM Message received: " << message.sender << message.text;
    emit onMessage(message, this);
}

/* Roster part received */
void QOscar::onRosterReceived()
{
#ifdef OSCARDEBUG
    qDebug() << "Roster received!";
#endif
    emit onRoster(this);
    // ask server for activate presence notifications AFTER roster processing
    sendFlap(SNAC_CHANNEL, feedbag.createFEEDBAG__USE());
}

/* On Ping timer */
void QOscar::onPingTimer()
{
    sendFlap(KEEP_ALIVE_CHANNEL);
}

/* Offline message received */
void QOscar::onOfflineMessage(const QOfflineMessage &message)
{
#ifdef OSCARDEBUG
    qDebug() << "Offline message:" << message.sender << message.time << message.text;
#endif
    emit onOfflineMessageReceived(message);
}

/* All Offline messages received */
void QOscar::onOfflineMessagesDone()
{
    sendFlap(SNAC_CHANNEL, imd.createIMD__ACK_OFFLINE_MESSAGES(oscarSN)); // Temporary!!
}

/* Buddy arrived */
void QOscar::onBuddyArrived(const QRosterEntry &entry)
{
#ifdef OSCARDEBUG
    qDebug() << "Buddy arrived:" << entry.name;
#endif
    emit onArrived(entry, this);
}

/* Buddy departed */
void QOscar::onBuddyDeparted(const QRosterEntry &entry)
{
#ifdef OSCARDEBUG
    qDebug() << "Buddy departed:" << entry.name;
#endif
    emit onDeparted(entry, this);
}
